package com.rusefi.output;

import com.rusefi.ConfigField;
import com.rusefi.ReaderState;
import com.rusefi.util.LazyFile;

import java.io.IOException;
import java.util.*;

import static com.rusefi.output.JavaSensorsConsumer.quote;

/**
 * here we tell the firmware what to log on SD card how
 *
 * @see DataLogConsumer
 */
public class SdCardFieldsContent {
    public static final String SD_CARD_OUTPUT_FILE_NAME = "console/binary/generated/log_fields_generated.h";
    public static final String BOARD_LOOKUP_H = "#include \"board_lookup.h\"\n";
    private final Map<java.util.Optional<String>, List<String>> collectedFieldsMap = new HashMap<>();

    public String[] expressions = {"test->reference"}; // technical debt: default value is only used by unit tests
    public String conditional;
    public Boolean isPtr = false;
    public String[] names;
    public int structureStartingTsPosition;

    public static void wrapContent(LazyFile output, String content) {
        wrapContent(output, content, "");
    }

    public static void wrapContent(LazyFile output, String content, String additionalHeaders) {
        output.write("// generated by " + SdCardFieldsContent.class + "\n");
        output.write("#pragma once\n");
        output.write(BOARD_LOOKUP_H);
        if (additionalHeaders != null && !additionalHeaders.isEmpty()) {
            output.write(additionalHeaders);
        }
        output.write("static LOG_FIELD_CONSTNESS_SPECIFIER_STORAGE MLG::Entries::Field fields[] = {\n" +
                "\t{packedTime, GAUGE_NAME_TIME, \"sec\", 0},\n");
        output.write(content);
        output.write("};\n");
    }

    public void handleEndStruct(ReaderState state, ConfigStructure structure) throws IOException {
        if (state.isStackEmpty()) {
            for (int i = 0; i < expressions.length; i++) {
                String namePrefix = getNamePrefix(i, names);
                String expression = expressions[i];
                appendFields(state, structure, namePrefix, expression);
            }
        }
    }

    public static String getNamePrefix(int i, String[] names) {
        return (names == null || names.length <= 1) ? "" : names[i];
    }

    private void appendFields(ReaderState state, ConfigStructure structure, String namePrefix, String expression) {
        PerFieldWithStructuresIterator.Strategy strategy = new PerFieldWithStructuresIterator.Strategy() {
            @Override
            public String process(ReaderState state, ConfigField configField, String prefix, int currentPosition, PerFieldWithStructuresIterator perFieldWithStructuresIterator) {
                String line = processOutput(configField, prefix, currentPosition, perFieldWithStructuresIterator, namePrefix, expression);
                java.util.Optional<String> key = java.util.Optional.ofNullable(conditional);
                collectedFieldsMap.computeIfAbsent(key, k -> new ArrayList<>()).add(line);
                return line;
            }

            @Override
            public String getArrayElementName(ConfigField cf) {
                return cf.getOriginalArrayName();
            }
        };

        PerFieldWithStructuresIterator iterator = new PerFieldWithStructuresIterator(state, structure.getTsFields(), "",
            strategy, ".");
        structureStartingTsPosition = iterator.loop(structureStartingTsPosition);
    }

    private String processOutput(ConfigField configField, String prefix, int currentPosition, PerFieldWithStructuresIterator perFieldWithStructuresIterator, String namePrefix, String expression) {
        if (configField.isUnusedField())
            return "";

        String name = configField.getOriginalArrayName();

        return getLine(configField, prefix, namePrefix, prefix + name, expression, isPtr, conditional, currentPosition, perFieldWithStructuresIterator, structureStartingTsPosition);
    }

    private static String getLine(ConfigField configField, String prefix, String outputNamePrefix, String name, String expression, Boolean isPtr, String conditional, int currentPosition, PerFieldWithStructuresIterator perFieldWithStructuresIterator, int structureStartingTsPosition) {
        String humanName = DataLogConsumer.getHumanGaugeName(outputNamePrefix, prefix, configField, "");

        String categoryStr = configField.getCategory();

        if (categoryStr == null) {
            categoryStr = "";
        } else {
            categoryStr = ", " + categoryStr;
        }

        boolean isEnum = configField.getTypeName().contains("_e");
        if (isEnum)
            return "";

        if (configField.isBit()) {
            // 'structureStartingTsPosition' is about fragment list see fragments.h
            int offsetWithinCurrentStructure = currentPosition - structureStartingTsPosition;
            if (offsetWithinCurrentStructure < 0)
                throw new IllegalStateException(humanName + " seems broken: " + currentPosition + " vs " + structureStartingTsPosition);
            return
                "\t{" +
                (isPtr ? "*" : "") + expression +
                ", " + offsetWithinCurrentStructure +
                ", " + perFieldWithStructuresIterator.bitState.get() + ", "
                + humanName +
                ", " +
                quote(configField.getUnits()) +
                categoryStr +
                "},\n";
        } else {
            return
                "\t{" +
                expression + (isPtr ? "->" : ".") + name +
                ", "
                + humanName +
                ", " +
                quote(configField.getUnits()) +
                ", " +
                configField.getDigits() +
                categoryStr +
                "},\n";
        }
    }

    public void processCollectedFieldsBlocks(StringBuilder body) {
        Comparator<java.util.Optional<String>> optCmp = Comparator.comparing(
            (java.util.Optional<String> o) -> o.orElse(null),
            Comparator.nullsFirst(String::compareTo)
        );

        collectedFieldsMap.entrySet().stream().sorted(Map.Entry.comparingByKey(optCmp))
        .forEach(
            e -> {
                String conditionalKey = e.getKey().orElse(null);
                List<String> block = e.getValue();
                // process the block here, e.g., write to output file
                if (conditionalKey != null) {
                    body.append("#if ").append(conditionalKey).append("\n");
                }
                block.forEach(body::append);
                if (conditionalKey != null) {
                    body.append("#endif\n");
                }
            }
        );
    }

    public String getBody() {
        StringBuilder body = new StringBuilder();
        processCollectedFieldsBlocks(body);
        return body.toString();
    }
}
